﻿
#ifndef  CLIENT_SOCKET2_H
#define CLIENT_SOCKET2_H

#include "cocos2d.h"
#include "ODSocket.h"

#include "MemBlock2.h"
#include "GnsClientCoreHead.h"

USING_NS_CC;

#define MAX_LEN 1024*6
#define THREAD_SLEEP_ELAPSE  5  // 以 ms 为单位

// 客户端参数类型
enum CLIENT_PARAM_TYPE
{
	CPT_autoReConnectTime = 10,		// 断线后自动重连的间隔时间
	CPT_retryConnectMaxCount = 20,					// 重连的最大次数
	CPT_curRetryCount = 30,					// 当前重连次数
	CPT_sendHeartbeatElapse = 40,		// 发送心跳的间隔
};

enum SERVER_HANDLE_ERR	// 对端专用的错误提示
{
	SHE_CMemBlockPool = 5,		// CMemBlockPool2 是空值
	SHE_MemBlock_IsNull = 6,	// 消息块是空值
	SHE_PackageNotOver = 8,		// 包头若过来了，但包体还未发过来，就应该等到下一个包完整到达时才判断

	SHE_NOT_isValid = 20,				// !isValid()
	SHE_NOT_START = 30,				// !m_bStart.load()

	SHE_pCoreHead_iTotalSize_is_0 = 9000,	// pCoreHead->iTotalSize 是零值 

	SHE_pCommTcpServerIsNull = 1000,	// m_pCommTcpServer 是空值
	SHE_SmallPackegIsZero = 1002,	// 收到包的长度是零
	SHE_Verify_Fail = 1003,	// 校验包失败

	SHE_pAtomMsg_retrieve_Fail = 1008,	// 原子包出池失败

	SHE_CoreHeadFlagFail = 2000,	// 标志位判断失败，非 0xaaaaaaaaaaaaaaaa
	SHE_PackageLenFail = 2001,		// 包的长度判断失败

	SHE_m_pCoreClientIsNull = 3000,	// m_pCoreClient 是空值
	SHE_Verify_First_Fail = 3004,	// 校验包失败，第一个包是无效的
	SHE_Verify_Other_Fail = 3006,	// 校验包失败，第一个包之后出现无效包

	SHE_PackageActionUnknown = 4000,	// 包头请求动作不正确
	SHE_PackageTypeUnknown = 4001,	// 包头加密类型不正确
	SHE_PackageCRC32Error = 4002,	// 包头CRC32不正确
	SHE_PackageChecksumError = 4003,	// 包头校验和不正确
	SHE_PackageVerifyDone = 4004,	// 包头校验正确
	SHE_PackageSourceInValid = 4005,	// 原始包不存在
	SHE_PackageBodyLenIsZero = 4006,	// 包体长度为0
	SHE_PackageCommKeyInValid = 4007,	// 密钥不存在
};

// 客户端Socket 类
// 不用单例是因为工程里往往需要多个 ClientSocket 对象
class ClientSocket 
{
public:
	enum CORE_SOCKET_MSG	// 内核SOCKET消息
	{
		CORE_SOCKET_INVALID = 0,					// 无效值
		CORE_SOCKET_CONNECT = 5,					// 连接
		CORE_SOCKET_CLOSE = 10,						// 断线
		CORE_SOCKET_RECONNECT = 15,			// 断线后重新连接
		CORE_SOCKET_NO_HEATBEAT = 20,		// 无心跳
	};

	struct INTERIOR_INFO	// 客户端内部消息
	{
		INTERIOR_INFO()	
		{ 
			memset(this, 0, sizeof(*this));	 
			bManualConnect = 1;
		}

		// 1 表示手工连接，需要手工调用 ClientSocket 对象的 start 函数
		// 0 表示自动连接，只要 GnsClientFrame 对象的 start 一执行就自动连接啦
		int bManualConnect;	
	};

public:
	ClientSocket();
	~ClientSocket();

	virtual bool init();

	int connectSocket(bool bAutoReConnect = true);

	int stop();

	void  closeSocket(bool bAutoReConnect=false);

	/* --------------------------------------------------------------------------
	函数说明：发送数据
	传入参数：
		iMainCode			主码
		iSubCode			辅助码
		pData					要发送的数据
		iDataLen				要发送的数据长度
		szFunName		调用本指令时所在函数的名称，方便查错
	返回值：
		成功则返回 0，失败返回错误码
	--------------------------------------------------------------------------*/
	int sendMsg(int iMainCode, int iSubCode, char* pData, int iDataLen, char* szFunName=nullptr);

public:

	// 设置客户端 Socket 的信息
	void setClientInfo(int iClientId, const char* szServerIp, int iPort, int iMsgBlockSize = 1280, int bManualConnect = 1);

	// 设置重连信息
	void setReconnectInfo(int iAutoReConnectTime, int iRetryConnectMaxCount = RETRY_CONNECT_MAX_COUNT);

	// 接收到的消息容器的原子消息数量
	int getDequeAtomMsgSize();

	// 获得有效性
	bool isValid();

	// 根据客户端参数类型获得客户端参数值
	int getParamByType(CLIENT_PARAM_TYPE cpt);

private:

	/* --------------------------------------------------------------------------
	函数说明：发送数据
	传入参数：
		pMemBlock	要发送的消息块，由框架归池，无需手工归池
	返回值：
		成功则返回 0，失败返回错误码
	--------------------------------------------------------------------------*/
	int sendMsg(CMemBlock2* pMemBlock);

	// 直实发送数据
	int realSendMsg(CMemBlock2* pMemBlock);

	// 连接服务端
	bool connectServer();

	// 断线重连
	bool reconnectAfterOffline();

	/* --------------------------------------------------------------------------
	函数说明：制做成原子包，将 n 多个小包合成一个完整的包
	传入参数：
		pData			收到的数据
		iDataLen		收到的数据长度
	返回值：
		成功则返回 0，否则返回错误码参考 SERVER_HANDLE_ERR 枚举：
	--------------------------------------------------------------------------*/
	int makeAtomMsgBlock(const char* pData, int iDataLen);

	// 判断包的有效性
	int	isPackegValid(const char* pData, int iDataLen);

	// 发送心跳包
	int sendHeartbeatPackeg();

	// 接收线程
	void threadRecvData();

	// 发送线程
	void threadSendData();

	// 工人线程，专门处理 _dequeRecvMsg 消息
	void threadLogicWork();

	// 内核消息的工人线程
	void threadCoreWork();

public:

	// 模拟框架功能的定时器
	void timerFrameFun();

public:
		//INTERIOR_INFO	_interiorInfo;		// 客户端内部消息
		// 1 表示手工连接，需要手工调用 ClientSocket 对象的 start 函数
		// 0 表示自动连接，只要 GnsClientFrame 对象的 start 一执行就自动连接啦
		int _manualConnect = 1;

private:

    ODSocket _socket;

	std::thread									 _recvThread;			// 接收线程
	std::deque<CMemBlock2*>   _dequeRecvMsg;		// 接收到的消息容器，里面的元素已经全是原子包
	std::recursive_mutex									_mutexRecvMsg;		// 非递归互斥，针对 _dequeRecvMsg
	bool											_bRecvThread=false;			// 已建立接收线程

	std::thread									_logicWorkerThread;						// 逻辑消息的工人线程，专门处理 _dequeRecvMsg 消息
	bool											_bLogicWorkerThread = false;		// 已建立逻辑消息的工人线程

	std::thread									_sendThread;			// 发送线程
	std::deque<CMemBlock2*>   _dequeSendMsg;		// 发送消息容器，里面的元素已经全是原子包
	std::recursive_mutex									_mutexSendMsg;		// 非递归互斥，针对 _dequeSendMsg
	bool											_bSendThread=false;		// 已建立发送线程
	int												_sendMaxByteInOneCycle=1024*2;		// 一个发送周期最大的发送长度
	int												_sendThreadSleep=5;							// 发送线程的睡眠时间，ms 

	std::thread									_coreWorkerThread;			// 内核消息的工人线程
	std::deque<CORE_SOCKET_MSG>   _dequeCoreMsg;	// 内核消息容器
	std::recursive_mutex									_mutexCoreMsg;					// 非递归互斥，针对 _dequeCoreMsg
	bool											_bCoreWorkerThread=false;		// 已建立内核消息的工人线程

	std::thread									_frameFunThread;						// 框架功能线程
	bool											_bframeFunThread = false;		// 已建立框架功能线程

	// 消息存储箱，多个小包合成一个原子包，
	// 测试方法见 CRecvResult::recv_data() 函数中的 wsabuf.len = 3;	
	CMemBlock2*	m_pStoreBox=nullptr;	

	int		_msgBlockSize;	// 消息块的长度

	char recvBuf[MAX_LEN];		// 接收缓冲

	int		_clientId=0;			// 客户端 Socket 的 Id

	char		_serverIp[32];		// 要连接的服务器的 IP
	int		_port=5150;			// 要连接的服务器的 端口

	std::atomic<bool>		m_bStart;				// 已经开始了，ture 表示 start 状态， false 表示 stop 状态
	std::atomic<bool>		m_bReconnect;	// 是否连接服务器

	bool	_autoReConnect=true;			// 断线后自动重连
	int		_autoReConnectTime=800;	// 断线后自动重连的间隔时间，单位ms，即断线后多少ms才能再次重连
	int		_retryConnectMaxCount = RETRY_CONNECT_MAX_COUNT;				// 重连的最大次数
	int       _curRetryCount = 0;					// 当前重连次数

	int		_sendHeartbeatElapse;			// 发送心跳的间隔


};

#endif